package com.haole.ecommercepay.common;

import cn.hutool.http.HttpResponse;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.List;
import java.util.Map;

/**
 * @author haole
 * 签名相关
 */
public class SignUtils {

    private static final Logger logger = LoggerFactory.getLogger(SignUtils.class);

    /**
     * 签名生成
     * @param method 请求方法 如POST
     * @param urlSuffix
     * @param mchId
     * @param serialNo
     * @param body
     * @param mchPrivateKeyPath
     * @return
     */
    public static String authorization(String method,String urlSuffix,String mchId,String serialNo,String body,String mchPrivateKeyPath) {

        try {

            //商户私钥
            String mchPrivateKey = CertificateUtils.getPrivateKey(mchPrivateKeyPath);
            //时间戳
            String timestamp = Long.toString(System.currentTimeMillis()/1000);
            //随机数
            String nonceStr = UuidUtils.randomUUID();

            //拼签名串
            StringBuilder sb = signMessage(method,urlSuffix,timestamp,nonceStr,body);

            logger.info("sign original string:{}",sb.toString());

            //计算签名
            String sign = new String(Base64.encodeBase64(v3signRSA(sb.toString(),mchPrivateKey)));

            logger.info("sign result:{}",sign);

            //拼装http头的Authorization内容
            String authorization ="WECHATPAY2-SHA256-RSA2048 mchid=\""+mchId+"\",nonce_str=\""+nonceStr+"\",signature=\""+sign+"\",timestamp=\""+timestamp+"\",serial_no=\""+serialNo+"\"";

            logger.info("authorization result:{}",authorization);

            return authorization;

        } catch (Exception e) {
            logger.error("authorization Exception result:{}",e);
            e.printStackTrace();
            return null;
        }
    }

    public static String paySign(String mchAppid,String timestamp,String nonceStr,String packageParam,String mchPrivateKeyPath) {

        try {

            //商户私钥
            String mchPrivateKey = CertificateUtils.getPrivateKey(mchPrivateKeyPath);
            logger.info("timestamp：{}，nonceStr：{}",timestamp,nonceStr);

            //拼签名串
            StringBuilder sb = paySignMessage(mchAppid,timestamp,nonceStr,packageParam);

            logger.info("paySign original string:{}",sb.toString());

            //计算签名
            String paySign = new String(Base64.encodeBase64(v3signRSA(sb.toString(),mchPrivateKey)));

            logger.info("paySign result:{}",paySign);

            return paySign;

        } catch (Exception e) {
            logger.error("authorization Exception result:{}",e);
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Authorization 签名串
     * @param method
     * @param urlSuffix
     * @param timestamp
     * @param nonceStr
     * @param body
     * @return
     */
    private static StringBuilder signMessage(String method,String urlSuffix,String timestamp,String nonceStr,String body) {
        return new StringBuilder()
                .append(method)
                .append("\n")
                .append(urlSuffix)
                .append("\n")
                .append(timestamp)
                .append("\n")
                .append(nonceStr)
                .append("\n")
                .append(body)
                .append("\n");
    }

    /**
     * 支付签名串
     * @param mchAppid
     * @param timestamp
     * @param nonceStr
     * @param packageParam
     * @return
     */
    private static StringBuilder paySignMessage(String mchAppid,String timestamp,String nonceStr,String packageParam) {
        return new StringBuilder()
                .append(mchAppid)
                .append("\n")
                .append(timestamp)
                .append("\n")
                .append(nonceStr)
                .append("\n")
                .append(packageParam)
                .append("\n");
    }

    /**
     * 私钥签名
     * @param data 需要加密的数据
     * @param mchPriKey
     * @return
     * @throws Exception
     */
    public static byte[] v3signRSA(String data, String mchPriKey) throws Exception {
        //签名的类型
        Signature sign = Signature.getInstance("SHA256withRSA");
        //读取商户私钥,该方法传入商户私钥证书的内容即可
        byte[] keyBytes = Base64.decodeBase64(mchPriKey);
        PKCS8EncodedKeySpec keySpec =new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey priKey = keyFactory.generatePrivate(keySpec);
        sign.initSign(priKey);
        sign.update(data.getBytes(StandardCharsets.UTF_8));
        return sign.sign();
    }

    public static boolean v3VerifyRSA(String data,byte[] sign, String wechatPubKeyPath) throws Exception{
        if(data == null || sign == null || wechatPubKeyPath == null){
            return false;
        }

        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        FileInputStream in = new FileInputStream(wechatPubKeyPath);
        Certificate c = cf.generateCertificate(in);
        in.close();
        PublicKey publicKey = c.getPublicKey();
        Signature signature = Signature.getInstance("SHA256WithRSA");
        signature.initVerify(publicKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));

        boolean result = signature.verify(sign);
        if (result) {
            logger.info("v3VerifyRSA result:{}","签名验证成功");
        } else {
            logger.info("v3VerifyRSA result:{}","签名验证失败");
        }
        return result;

    }

    /***
     * 加密敏感信息
     * @param message
     * @param certificate
     * @return
     */
    public static String rsaEncryptOAEP(String message, X509Certificate certificate)
            throws IllegalBlockSizeException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());

            byte[] data = message.getBytes("utf-8");
            byte[] cipherdata = cipher.doFinal(data);
            return Base64.encodeBase64String(cipherdata);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的证书", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }

    /**
     * 解密敏感信息
     * @param ciphertext
     * @param privateKey
     * @return
     * @throws BadPaddingException
     * @throws IOException
     */
    public static String rsaDecryptOAEP(String ciphertext, PrivateKey privateKey)
            throws BadPaddingException, IOException {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            byte[] data = Base64.decodeBase64(ciphertext);
            return new String(cipher.doFinal(data), StandardCharsets.UTF_8);
        } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的私钥", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            throw new BadPaddingException("解密失败");
        }
    }

    /**
     * 签名验证
     * @param response
     * @param wechatPubKeyPath
     * @return
     */
    public static boolean v3VerifyRSA(HttpResponse response,String wechatPubKeyPath) {

        if (response == null || StringUtils.isEmpty(wechatPubKeyPath)) {
            return false;
        }

        Map<String, List<String>> headers = response.headers();

        //验证微信支付返回签名
        String headsTimestamp = headers.get("Wechatpay-Timestamp").get(0);
        String headsNonce = headers.get("Wechatpay-Nonce").get(0);
        String headsSign = headers.get("Wechatpay-Signature").get(0);

        String resContent = response.body();

        //拼装待签名串
        StringBuilder sb =new StringBuilder();
        sb.append(headsTimestamp).append("\n");
        sb.append(headsNonce).append("\n");
        sb.append(resContent).append("\n");

        try {
            //验证签名
            return v3VerifyRSA(sb.toString(), Base64.decodeBase64(headsSign.getBytes()), wechatPubKeyPath);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}
